
/**
 * Auteur : Dominique GUERIN
 * dominiquephDOTguerinATgmailDOT..
 * dominiqueDOTguerinATinseeDOTfr(ance)  
 * Remerciements: � Keith Brown pour ses deux livres sur la s�curit� sous Windows et ses diff�rents articles
 *                 et � Michel Barnett de Microsoft pour ses deux articles 
 *                ".NET Remoting Authentication and Authorization Sample"  et le code qui les accompagne
 *  Le code est utilisable, modifiable et redistribuable � volont� sous la condition de ne pas supprimer ces 7 lignes.
 */ 

package fr.doume.jna.base64;


//tomcat 5.5
//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;

//tomcat 6
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;


import fr.doume.jna.base64.FormatBase64Exception;

 
public final class Base64
{
  private static Log log = LogFactory.getLog(Base64.class);
  
  private Base64(){}
  
  /**
  * Map Base 64 encoding to char
  */
  private static char[] charsbase64 = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
                                         'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d',
                                         'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s',
                                         't','u','v','w','x','y','z','0','1','2','3','4','5','6','7',
                                         '8','9','+','/','=' };

  /**
  * The array of bytes is translated into an array of packets of six bits(2^6 = 64).
  * The lengh of the last array must be divisible by 4.
  * '=' is used to padding. 
  */
  public static byte[] encodeTo64( byte[] base255)
  {
    int len_base255 = base255.length;
    int nb_3tuples = len_base255 /3;
    int reste_a = len_base255 %3;
    int nb_derniers_car_b = (reste_a == 0 ? 0:4);

    int len_b = nb_3tuples * 4 + nb_derniers_car_b;
    byte[] b = new byte[len_b];
    
    int pos_a = 0, pos_b = 0;
    byte l, m, n;
    int b1, b2, b3, b4;
    int b1b,b2b,b3b,b4b;

    for (int i = 0 ; i < nb_3tuples; i++)
    {
      l = base255[pos_a ++];
      m = base255[pos_a ++];
      n = base255[pos_a ++];
      //log.debug("l: " + l + " m: " +m +  " n: " + n);
      
      //1) l, m, n are signed => the masks are required
      //        The shift >>> is not sufficient.
      //        There is not unsigned int in java, so we must use a mask
      //        The logical shift (>>>) is not sufficient. The calculations use ints
      //        ( java specification) . When we cast a byte, with the first link bit set to 1, to an int, all the 
      //          link bits of the int are 1.
      //          There is no unsigned int in java.           
      //2) The stream of bytes (8 bits) is cut by 6_tuples of bits
      //3)    The right shifts are processed on the 8 bits 
      //              ( count of kept positions + offset == 8)
      //4)    The link shifts to define the last positions on the sixth bit. 
      //              ( count of kept positions + offset == 6)
      b1=0;b2=0;b3=0;b4=0;
      b1 =  ( l >> 2 ) & 0x3F;                // 6 first positions of l (remove the 2 last positions)
      b2 = ( ( l << 4) & 0x30) |   ((m >> 4) & 0xF) ;   // 2 last positions of l and the 4 first positions of m
      b3 = ( (m  << 2 ) & 0x3C) | ((n >> 6) & 0x3);        // 4 last positions of m et 2 last of n
      b4 =  n & 0x3F;                      // 6 last positions of n
 
      b[pos_b ++] = (byte)charsbase64[b1];
      b[pos_b ++] = (byte)charsbase64[b2];
      b[pos_b ++] = (byte)charsbase64[b3];
      b[pos_b ++] = (byte)charsbase64[b4];
    }
    if ( reste_a == 2 )
    {
      l = base255[pos_a];
      m = base255[pos_a + 1];
      
      b1=0;b2=0;b3=0;b4=0;
      b1 = ( l >> 2 ) & 0x3F;                // 6 first positions of l
      b2 = (( l << 4 )& 0x30) |   ((m >>> 4) & 0xF) ;   // 2 last position of l and the 4 firts positions of m
      b3 = (m  << 2 ) & 0x3C;        // 4 last positions of m and  4 zeros
      b4 = 64;

      b[pos_b] = (byte)charsbase64[b1];
      b[pos_b + 1] = (byte)charsbase64[b2];
      b[pos_b + 2] = (byte)charsbase64[b3];
      b[pos_b + 3] = (byte)charsbase64[b4];

    }
    else if ( reste_a == 1 )
    {
      l = base255[pos_a];
      
      b1=0;b2=0;b3=0;b4=0;
      b1 = ( l >> 2 ) & 0x3F;                // 6 firts positions of  l
      b2 = ( l << 4 ) & 0x30 ;  // 2 last positions of l and 4 zeros
      b3 = 64;  
      b4 = 64;

      b[pos_b] = (byte)charsbase64[b1];
      b[pos_b + 1] = (byte)charsbase64[b2];
      b[pos_b + 2] = (byte)charsbase64[b3];
      b[pos_b + 3] = (byte)charsbase64[b4];

    }
    
    return b;

  }

 /**
 * Cut the stream of 6_tuples of bits into 8_tuples.
 * the char '=' is used to pad. Decoding can throw an Exception if the parameter is not in base64
 */ 
 
  public static byte[] decodeFrom64(byte[] base64)
  throws FormatBase64Exception
  {
    log.debug("count of received bytes " + base64.length); 
    base64 = removeNon64CharAndVerifyCoherence(base64);
    log.debug("count of received bytes after verification " + base64.length); 

    int nb_4tuples = base64.length / 4;
    
    int length_base255 = nb_4tuples * 3;
        
    if( base64[base64.length -1] == (byte) '=' ) length_base255 --;
    if( base64[base64.length -2] == (byte) '='  ) length_base255 --;        
    byte[] base255 = new byte[length_base255];
 
 
    int pos_64 = 0;
    int pos_255 = 0;
    byte c;
    byte b , b1, b2, b3, b4, l , m, n;
    
    log.debug("Count of 4-tuples : " + nb_4tuples);
    log.debug("length_base255 : (Count of bytes after the translation)" + length_base255);

    for (int i = 0; i< nb_4tuples; i++)
    {

      c = base64[pos_64 ++];
      b1 = getByteFromCharBase64(c);

      c = base64[pos_64 ++];
      b2 = getByteFromCharBase64(c);
       
      c = base64[pos_64 ++];
      b3 = getByteFromCharBase64(c);

      c = base64[pos_64 ++];
      b4 = getByteFromCharBase64(c);

      //1) bytes are not signed ( < 2^16 or 64)
      //      => The result of the casting to int is positive(Java specification)
      //            => no mask
      //2) The bytes with 6 bits (or 7 if = � 64)  wil be cut in 8-tuples
      //3)    The right shifts to define the last positions on the sixth bit 
      //              ( count of kept positions + offset == 6)
      //4)    The link shifts are calculed to use the 8 bits of the bytes. 
      //              ( count of kept positions + offset == 8)
      //
      if (b4 != 64) // not the char '='
      {
        l = (byte)(b1 << 2 | b2 >> 4); // The last 6 bits of b1 and the 2 fist of b2 (on 6 bits)
        base255[pos_255 ++] = l;
        
        m = (byte)(b2 << 4 | b3 >> 2);// The last 4 bits of b2 and the 4 first of b3 (on 6 bits)
        base255[pos_255 ++] = m;
        
        n = (byte)(b3 << 6 | b4 ) ;// The last 2 bits of b3 and the 6 bits of b4
        base255[pos_255 ++] = n;
      }
      else if (b3 != 64) // only 1 char '='
      {
        l = (byte)(b1 << 2 | b2 >> 4); // The last 6 bits of b1 and the 2 fist of b2 (on 6 bits)
        base255[pos_255 ++] = l;

        m = (byte)(b2 << 4 | b3 >> 2);// The last 4 bits of b2 and the 4 first of b3 (on 6 bits)
        base255[pos_255 ++] = m;
      }
      else  // 2 chars '='
      {
        l = (byte)(b1 << 2 | b2 >> 4); // The last 6 bits of b1 and the 2 fist of b2 (on 6 bits)
        base255[pos_255 ++] = l;
      }
       
    }
    return base255;
    
  }
 

  /**
  * Translate the ascii encoding of a char into the base64 encoding of the same char  
  */
  private static byte getByteFromCharBase64( byte c)
  {
      int b;
      if (c >= (byte)'A' && c <= (byte)'Z')
      {
        b = c - (byte) 'A';
      }
      else if ( c >=  (byte) 'a' && c<= (byte)'z')
      {
        b = c -  (byte) 'a' + 26;
      }
      else if( c >= (byte)'0' && c <= (byte)'9')
      {
        b = c - (byte)'0' + 52;
      }
      else if (c == '+') b = 62;
      else if(c == '/' ) b = 63;
      else b = 64;
      
      return (byte)b;

  }
  
  private static byte[] removeNon64CharAndVerifyCoherence(byte[] b)
  throws FormatBase64Exception
  {
    // Remove the non Base64 chars
    byte [] bis = new byte[b.length];
    int j = 0;
    // Count of the char '='
    int nbAssignement =0; 
    for (int i = 0; i < b.length; i++)
    {
      if( b[i] == (byte) '=')
      //Only the two last chars could be '='
      {
        if (nbAssignement == 0 || nbAssignement == 1 )
        {
          nbAssignement ++;
          bis[j] = b[i];
          j++;
        }
        else
        {
        	if (log.isDebugEnabled()) log.debug("Inconsistent data in base 64 : too many '=' ");
          throw new FormatBase64Exception("Inconsistent data in base 64  : too many '=' ");
        } 
      }
      else if (isChar64(b[i]))
      {
        if (nbAssignement == 0)
        {        
          bis[j] = b[i];
          j++;
        }
        else
        {
          if (log.isDebugEnabled()) log.debug("Inconsistent data in base 64 : a '=' inside the characters");
          throw new FormatBase64Exception("Inconsistent data in base 64 : a '=' inside the characters");
        }
      }
    }
    
    
    if (j %4 != 0)
    {
      if (log.isDebugEnabled()) log.debug("Inconsistent data in base 64  : length is not a multiple opf 4 ");
      throw new FormatBase64Exception("Inconsistent data in base 64  : length is not a multiplle opf 4 ");
    }  
    //copy of the arry
    byte[] result = new byte[j];
    System.arraycopy(bis, 0, result, 0, j);
    return result;
  }
  
  private static boolean isChar64( byte b)
  {
    return( 
              (b >= (byte) 'A' && b <= (byte) 'Z') ||
              (b >= (byte) 'a' && b <= (byte) 'z') ||
              (b >= (byte) '0' && b <= (byte) '9') ||
              (b == (byte) '+' )  || 
              (b== (byte) '/' )
            );
    
  }

}
